package com.tl.satoken.netty;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.tl.satoken.domain.entity.GameMessage;
import com.tl.satoken.domain.entity.Player;
import com.tl.satoken.enums.MessageType;
import com.tl.satoken.listener.WebSocketEventListener;
import com.tl.satoken.service.RoomService;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
//@Component
@ChannelHandler.Sharable
public class WebSocketFrameHandler extends SimpleChannelInboundHandler<Object> {

    // todo 维护 广播的关键性代码
    private static final ConcurrentHashMap<String, ChannelHandlerContext> clients = new ConcurrentHashMap<>();  // clientId 与 ChannelHandlerContext 的映射

    @Autowired
    private  RoomService roomService;
    @Autowired
    private  WebSocketEventListener webSocketEventListener;
    private static ObjectMapper objectMapper = new ObjectMapper();
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof TextWebSocketFrame) {
            String text = ((TextWebSocketFrame) msg).text();
            String clientId = ctx.channel().id().asShortText();
            log.info("收到客户端[{}]消息: {}", clientId, text);
            
            try {
                GameMessage gameMessage = objectMapper.readValue(text, GameMessage.class);
                handleGameMessage(ctx, clientId, gameMessage);
            } catch (Exception e) {
//                log.error("解析游戏消息失败", e);
//                sendError(ctx, "消息格式错误");
            }
        } else if (msg instanceof BinaryWebSocketFrame) {
            log.info("收到二进制消息");
        } else if (msg instanceof CloseWebSocketFrame) {
            log.info("客户端请求关闭连接");
            ctx.channel().close();
        } else if (msg instanceof PingWebSocketFrame) {
            log.info("收到Ping消息");
            ctx.channel().writeAndFlush(new PongWebSocketFrame());
        }
    }
    
    private void handleGameMessage(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        if (roomService == null || webSocketEventListener == null) {
            return;
        }
        try {
            switch (message.getType()) {
                case JOIN_GAME:
                    handleJoinGame(ctx, clientId, message);
                    break;
                case LEAVE_GAME:
                    handleLeaveGame(ctx, clientId, message);
                    break;
                case UPDATE_PLAYER:
                    handleUpdatePlayer(ctx, clientId, message);
                    break;
                case EMOJI_SENT:
                    handleEmojiSent(ctx, clientId, message);
                    break;
                
                default:
                    log.warn("未知消息类型: {}", message.getType());
            }
        } catch (Exception e) {
            log.error("处理游戏消息失败", e);
            sendError(ctx, "处理消息失败: " + e.getMessage());
        }
    }
    
    private void handleJoinGame(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        try {
            Player player = objectMapper.convertValue(message.getData(), Player.class);
            player.setSessionId(clientId);
            player.setChannel(ctx);
            
            log.info("玩家通过WebSocket加入游戏：{}，会话ID: {}", player.getUsername(), clientId);
            // todo 这里可以不要加入到map，加入到redis里面，clinet -> player，这里添加了，那么业务层（roomService）就不需要再加了
            webSocketEventListener.addSessionPlayer(clientId, player);

            String playerKey = "game:player:" + player.getId();
            // todo 有时候这个键拿不到，难道是有过期时间？
            String roomId = (String)redisTemplate.opsForValue().get(playerKey);
            if (roomId != null) {
                // todo检查一下这个 roomPlayers 的id属性存放是 id 还是clientId 。答： 就是ID
                List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
                // 广播给房间内所有玩家，通知有新玩家连接（包括自己，因为需要同步房间状态）
                broadcastRoomUpdate(roomId, roomPlayers);
                log.info("玩家 {} 已通过HTTP加入房间 {}，通过WebSocket连接成功", player.getUsername(), roomId);
            } else {
                var result = roomService.joinRoom(player);
                if (result.isSuccess()) {
                    List<Player> roomPlayers = roomService.getPlayersInRoom(result.getRoomId());
                    // 广播给房间内所有玩家，通知有新玩家加入（包括自己，因为需要同步房间状态）
                    broadcastRoomUpdate(result.getRoomId(), roomPlayers);
                    log.info("玩家 {} 成功加入房间 {}", player.getUsername(), result.getRoomId());
                } else {
                    log.warn("玩家 {} 加入房间失败: {}", player.getUsername(), result.getMessage());
                    sendError(ctx, result.getMessage());
                }
            }
        } catch (Exception e) {
            log.error("处理加入游戏消息失败", e);
            sendError(ctx, "加入游戏失败: " + e.getMessage());
        }
    }
    
    private void handleLeaveGame(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        try {
            Player player = objectMapper.convertValue(message.getData(), Player.class);
            log.info("玩家主动下线：{}，会话ID: {}", player.getUsername(), clientId);
            webSocketEventListener.removeSessionPlayer(clientId);
            
            if (roomService.leaveRoom(player.getId())) {
                String roomId = player.getRoomId();
                List<Player> remainingPlayers = roomService.getPlayersInRoom(roomId);
                // 广播给房间内剩余玩家，通知有玩家离开（离开的玩家已断开连接，无需排除）
                broadcastRoomUpdate(roomId, remainingPlayers);
                log.info("房间 {} 剩余玩家数量: {}", roomId, remainingPlayers.size());
            }
        } catch (Exception e) {
            log.error("处理离开游戏消息失败", e);
        }
    }
    
    private void handleUpdatePlayer(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        try {
            Player player = objectMapper.convertValue(message.getData(), Player.class);
            log.info("玩家移动：{}，位置：({}, {})，会话ID: {}", 
                player.getUsername(), player.getPosition().getX(), player.getPosition().getY(), clientId);
            
            // 更新session中的玩家信息
            Player sessionPlayer = webSocketEventListener.getPlayer(clientId);
            if (sessionPlayer != null) {
                sessionPlayer.setPosition(player.getPosition());
                // 同步更新其他字段
                sessionPlayer.setStatus(player.getStatus());
                sessionPlayer.setRoomId(player.getRoomId());
            }
            // updatePlayerPosition 更新redis中玩家的的位置
            if (roomService.updatePlayerPosition(player.getId(), player)) {
                String roomId = player.getRoomId();
                // 只广播移动玩家的位置信息，排除发送者自己
                broadcastPlayerMove(roomId, player);
            } else {
                log.warn("玩家 {} 位置更新失败", player.getUsername());
            }
        } catch (Exception e) {
            log.error("处理玩家移动消息失败", e);
        }
    }
    
    private void handleEmojiSent(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        try {   
            Player sessionPlayer = webSocketEventListener.getPlayer(clientId);
            if (sessionPlayer == null) {
                log.warn("发送表情包的玩家会话不存在: {}", clientId);
                return;
            }
            
            String roomId = sessionPlayer.getRoomId();
            if (roomId == null) {
                log.warn("玩家 {} 不在任何房间中", sessionPlayer.getUsername());
                return;
            }
            
            log.info("玩家 {} 发送表情包，房间: {}", sessionPlayer.getUsername(), roomId);
            
            // 广播表情包消息给房间内其他玩家，排除发送者自己
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            broadcastEmojiMessage(roomId, message, sessionPlayer.getId());
            
        } catch (Exception e) {
            log.error("处理表情包消息失败", e);
        }
    }
    

    
    private void broadcastRoomUpdate(String roomId, List<Player> players) {
        broadcastRoomUpdate(roomId, players, null);
    }
    
    private void broadcastRoomUpdate(String roomId, List<Player> players, String excludePlayerId) {
        try {
            GameMessage updateMessage = new GameMessage(MessageType.ROOM_UPDATE, "SERVER", roomId, players);
            String messageJson = objectMapper.writeValueAsString(updateMessage);
            
            for (Player player : players) {
                // 如果指定了排除的玩家ID，则跳过该玩家
                if (excludePlayerId != null && excludePlayerId.equals(player.getId())) {
                    continue;
                }
                // todo 这个ID是clientId？  答： 就是ID，根据ID获取对应的 player，player 里面有channel对象
                Player sessionPlayer = webSocketEventListener.getPlayerById(player.getId());
                if (sessionPlayer != null && sessionPlayer.getChannel() != null && sessionPlayer.getChannel().channel().isActive()) {
                    sessionPlayer.getChannel().channel().writeAndFlush(new TextWebSocketFrame(messageJson));
                }
            }
        } catch (Exception e) {
            log.error("广播房间更新失败", e);
        }
    }
    
    private void broadcastPlayerMove(String roomId, Player movedPlayer) {
        try {
            // 创建只包含移动玩家位置信息的消息
            GameMessage moveMessage = new GameMessage(MessageType.PLAYER_MOVE, "SERVER", roomId, movedPlayer);
            String messageJson = objectMapper.writeValueAsString(moveMessage);
            
            // 获取房间内所有玩家
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            int broadcastCount = 0;
            
            for (Player player : roomPlayers) {
                // 排除移动的玩家自己
                if (movedPlayer.getId().equals(player.getId())) {
                    continue;
                }

                Player sessionPlayer = webSocketEventListener.getPlayerById(player.getId());
                if (sessionPlayer != null && sessionPlayer.getChannel() != null && sessionPlayer.getChannel().channel().isActive()) {
                    sessionPlayer.getChannel().channel().writeAndFlush(new TextWebSocketFrame(messageJson));
                    broadcastCount++;
                } else {
                    log.warn("玩家 {} 的session不存在或连接已断开", player.getUsername());
                }
            }
            
            log.debug("向房间 {} 中的 {} 个玩家广播了玩家 {} 的移动消息", roomId, broadcastCount, movedPlayer.getUsername());

        } catch (Exception e) {
            log.error("广播玩家移动消息失败", e);
        }
    }
    
    private void broadcastEmojiMessage(String roomId, GameMessage emojiMessage, String excludePlayerId) {
        try {
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            String messageJson = objectMapper.writeValueAsString(emojiMessage);
            
            for (Player player : roomPlayers) {
                // 排除发送者自己
                if (excludePlayerId != null && excludePlayerId.equals(player.getId())) {
                    continue;
                }
                
                Player sessionPlayer = webSocketEventListener.getPlayerById(player.getId());
                if (sessionPlayer != null && sessionPlayer.getChannel() != null && sessionPlayer.getChannel().channel().isActive()) {
                    sessionPlayer.getChannel().channel().writeAndFlush(new TextWebSocketFrame(messageJson));
                }
            }
        } catch (Exception e) {
            log.error("广播表情包消息失败", e);
        }
    }
    
    private void sendError(ChannelHandlerContext ctx, String errorMessage) {
        try {
            GameMessage errorMsg = new GameMessage(MessageType.ERROR, "SERVER", null, errorMessage);
            String messageJson = objectMapper.writeValueAsString(errorMsg);
            ctx.channel().writeAndFlush(new TextWebSocketFrame(messageJson));
        } catch (Exception e) {
            log.error("发送错误消息失败", e);
        }
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        String clientId = ctx.channel().id().asShortText();
        clients.put(clientId, ctx);
        log.info("客户端[{}]连接成功，当前在线人数: {}", clientId, clients.size());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        String clientId = ctx.channel().id().asShortText();
        clients.remove(clientId);
        log.info("客户端[{}]断开连接，当前在线人数: {}", clientId, clients.size());
        
        if (webSocketEventListener != null) {
            var player = webSocketEventListener.getPlayer(clientId);
            if (player != null && player.getRoomId() != null) {
                roomService.leaveRoom(player.getId());
                List<Player> remainingPlayers = roomService.getPlayersInRoom(player.getRoomId());
                // 广播给房间内剩余玩家，通知有玩家断开连接（断开的玩家已离开，无需排除）
                broadcastRoomUpdate(player.getRoomId(), remainingPlayers);
            }
            webSocketEventListener.removeSessionPlayer(clientId);
            
            // 定期清理无效session
            if (clients.size() % 10 == 0) { // 每10个连接清理一次
                webSocketEventListener.cleanupInvalidSessions();
                log.info("清理无效session完成，当前有效session数: {}", webSocketEventListener.getValidSessionCount());
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("WebSocket异常", cause);
        ctx.close();
    }

    public static void broadcastToAll(String message) {
        clients.forEach((clientId, context) -> {
            context.channel().writeAndFlush(new TextWebSocketFrame(message));
        });
    }

    public static int getOnlineCount() {
        return clients.size();
    }
}
